package Node;

import java.io.FileReader;
import java.util.ArrayList;

import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TJSONProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransportException;
import org.ini4j.Ini;
import sun.misc.Signal;

import HBFailureService.*;
import KVService.KVHandler;
import KVService.KVService;
import TopologyService.*;
import RMService.*;
import QueryService.*;

/**
 * Class: Node
 *
 */
public class Node {
    
	/**
	 * Function to display the usage
	 */
	public static void DisplayUsage() {
	    System.out.println("\nNode - Used to launch a node process\n");
	    System.out.println("\tUsage: ./Node <services-ini> <topology-ini>");
	}
	
	/**
	 * Function to parse the addresses from a given .ini file containing
	 * an Addresses section 
	 * 
	 * @param addresses the .ini section containing addresses
	 * @return an arrayList of Strings of the addresses
	 */
	public static ArrayList<String[]> ParseAddresses(String addresses) {
	    // Split the addresses string into tokens
	    String[] tokens = addresses.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
        
        // Create an arrayList of strings
        ArrayList<String[]> hosts = new ArrayList<String[]>();
        
        // Loop over the tokens
        int i = 1;
        System.out.println("Printing host information:");
        for (String t : tokens) {
            // Remove whitespace
            String entry = t.replaceAll("\\s", "");
            
            // Print out the host information
            System.out.println("\tHost " + i + ": " + entry);
            
            // Add the host to the list to be returned
            hosts.add(entry.split(":"));
            
            i++;
        }
        
        // Return the list of hosts
        return hosts;
	}
	
	/**
	 * Constructor for Node class
	 */
	public Node(){};
	
	
	public static void joinNode(Topology topology) throws InterruptedException {
		
		
		// join next node
		TSocket socketNext = new TSocket(topology.getNextIP(), topology.getNextPort());
		TSocket socketPrev = new TSocket(topology.getPrevIP(), topology.getPrevPort());
		boolean connectedNext = false;
		boolean connectedPrev = false;
		
		while(!connectedPrev || !connectedNext){
			
			if(!connectedNext){
				try{
					TBinaryProtocol protocol = new TBinaryProtocol(socketNext);
					TMultiplexedProtocol mp = new TMultiplexedProtocol(protocol, "TopologyService");
					TopologyService.Client topologyProto = new TopologyService.Client(mp);
					socketNext.open();
					topologyProto.prevJoin(topology.myID());
					socketNext.close();
					System.out.println("Process " + topology.myID() + " connected to next node " + topology.getNextID());
					connectedNext = true;
				}
				catch(Exception e){
					System.out.println("Process " + topology.myID() + " failed to connect to next node " + topology.getNextID());
					Thread.sleep(1000);
				}
			}
			
			if(!connectedPrev){
				try{
					TBinaryProtocol protocol = new TBinaryProtocol(socketPrev);
					TMultiplexedProtocol mp = new TMultiplexedProtocol(protocol, "TopologyService");
					TopologyService.Client topologyProto = new TopologyService.Client(mp);
					socketPrev.open();
					topologyProto.nextJoin(topology.myID());
					socketPrev.close();
					System.out.println("Process " + topology.myID() + " connected to prev node " + topology.getPrevID());
					connectedPrev = true;
				}
				catch(Exception e){
					System.out.println("Process " + topology.myID() + " failed to connect to prev node " + topology.getPrevID());
					Thread.sleep(1000);
				}
			}
			
		}
	}

	/**
	 * Main entry point
	 * 
	 * @param args variable argument list
	 */
	public static void main(String[] args) {
	    
	    // Check for the correct number of arguments to be passed in
	    if (args.length != 2) {
	        // Print out an error message to the screen
	        System.out.println("Error: Incorrect number of input parameters!");
	        // Call function to display usage
	        Node.DisplayUsage();
	        // Exit the program
	        System.exit(0);
	    }
	    
	    // Create an ini object for the heartbeat information
	    Ini servicesIni = new Ini();
	    // Create an ini object for the topology information
	    Ini topologyIni = new Ini();
	    
	    try {
	    	
	        // Load the heartbeat and topology sections from the input .ini files
	    	servicesIni.load(new FileReader(args[0]));
	        topologyIni.load(new FileReader(args[1]));
	        
	        // Parse the addresses section from the topology ini file
	        Ini.Section section = topologyIni.get("Nodes");
	        String addresses = section.get("Addresses");
	        ArrayList<String[]> hosts = Node.ParseAddresses(addresses);
	        
	        // Parse out the process id from the heartbeat .ini file passed in
	        section = servicesIni.get("Process");
	        String procID = section.get("ProcessID");
	        Topology topology = new Topology(Integer.parseInt(procID), hosts);
	        
		    // add signal handler
		    Signal.handle(new Signal("TERM"), new SigTermHandler (procID));
	        
	        // Create a server using the port number retrieved from the topology file
	        // Note: This is the port that the server listens on
	        String[] serverCfg = hosts.get(Integer.parseInt(procID));
	        Server server = new Server(Integer.parseInt(serverCfg[1]));
	        
	        // Register the heartbeat failure service with the server
	        section = servicesIni.get("HeartBeatMonitor");
	        int timeout = Integer.parseInt(section.get("HeartbeatTimeout"));
			server.registerHandler("HBFailureService", new HBFailureService.Processor(new HBHandler(topology, timeout)));
			
			// Initialize Topology
			section = servicesIni.get("Topology");
			topology.setNext(Integer.parseInt(section.get("nextID")));
			topology.setPrev(Integer.parseInt(section.get("prevID")));
			server.registerHandler("TopologyService", new TopologyService.Processor(new TopologyHandler(topology))); 
	        
	        // Register the kv service with the server
			KVHandler kvhandler = new KVHandler(servicesIni, topology);
	        server.registerHandler("KVService", new KVService.Processor(kvhandler));
	        
	        // Register the RM service with the server
	        // The RM service is started after the node has joined the topology since it needs the topology to bootstrap itself
	        section = servicesIni.get("rm");
	        int ttl = Integer.parseInt(section.get("ttl"));
	        RMHandler rmHandler = new RMHandler(topology, ttl, kvhandler);
	        server.registerHandler("RMService", new RMService.Processor(rmHandler));
	        
	        // Register the Query service with the server
	        QueryHandler queryHandler = new QueryHandler(topology, ttl, kvhandler);
	        server.registerHandler("QueryService", new QueryService.Processor(queryHandler));
	        
	        // Start up the server in a new thread
	        new Thread(server).start();
	        
	        // Join topology
	        Node.joinNode(topology);
	        
	        // Initialize replication manager
	        rmHandler.init();
	        
	        
	        // Create Heartbeat Client (one client per 2 heart beat targets)
	        section = servicesIni.get("HeartBeatClient");
	        timeout = Integer.parseInt(section.get("HeartbeatTimeout"));
	        new Thread(new HBClient(topology, timeout)).start();
	        	        
	    } 
	    catch (Exception e) {
	        // Print out the stack trace if an exception occurs
	        e.printStackTrace();
	    }
	}
}
